﻿using System;
using System.Collections.Generic;

namespace barcodes
{
    /// <summary>
    /// Класс инкапсулирует алгоритм кодирования 12-ти значной последовательности в код по стандартку EAN-13
    /// </summary>
    public class Converter: IBarcodeCoder
    {
        private Dictionary<char, string> g_code = new Dictionary<char, string>();
        private Dictionary<char, string> l_code = new Dictionary<char, string>();
        private Dictionary<char, string> r_code = new Dictionary<char, string>();
        private int? _controlDigit;
        private int? _firstDigit;

        /// <summary>
        /// Конструктор конвертера в двоичное представление EAN-13
        /// </summary>
        public Converter()
        {
            char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
            string[] g_codes = { "0100111","0110011","0011011","0100001","0011101",
                                 "0111001","0000101","0010001","0001001","0010111"};
            string[] l_codes = { "0001101","0011001","0010011","0111101","0100011", 
                                 "0110001","0101111","0111011","0110111","0001011"};
            string[] r_codes = { "1110010","1100110","1101100","1000010","1011100",
                                 "1001110","1010000","1000100","1001000","1110100"};

            _controlDigit = null;
            _firstDigit = null;
            for (int i = 0; i != 10; i++)
            {
                g_code.Add(digits[i], g_codes[i]);
                l_code.Add(digits[i], l_codes[i]);
                r_code.Add(digits[i], r_codes[i]);
            }

        }
        /// <summary>
        /// Получить контрольную цифру для данной последовательности
        /// </summary>
        /// <returns>Контрольная цифра</returns>
        public int GetControlDigit()
        {
            if (_controlDigit != null)
                return _controlDigit.Value;
            else
                throw new ControlDigitDoesntEvaluatedException();
        }

        /// <summary>
        /// Возвращает строку с двоичным представлением штрихкода
        /// </summary>
        /// <param name="input">Входная строка</param>
        /// <returns>Двоичное представление закодированной строки</returns>
        /// <exception cref="BadInputException">Если во входной строке присутствуют не цифры</exception>
        public string GetBarcodePres(string input)
        {
            if (!IsInputCorrect(input))
            {
                throw new BadInputException();
            }

            string marc = "101";
            string mid = "01010";

            _firstDigit = Int32.Parse(input[0].ToString());

            string tmp = String.Empty;
            for (int i = 1; i < input.Length; i++)
            {
                tmp += input[i];
            }

            _controlDigit = EvalControlDigit(input);
            tmp += _controlDigit.ToString();

            string result = marc;
            result += CodeLeftPart(tmp);
            result += mid;
            result += CodeRightPart(tmp);
            result += marc;

            return result;

        }

        /// <summary>
        /// Проверяет входную строку на корректность
        /// </summary>
        /// <param name="input">Входная строка</param>
        /// <returns>true - Если в строке отсутствуют не цифры, false в противном случае</returns>
        private bool IsInputCorrect(string input)
        {
            if ((input.Length < 12) || (input.Length > 12))
            {
                return false;
            }
            for (int i = 0; i != input.Length; i++)
            {
                //48 < 57  - коды цифр
                if ((input[i] < 48) || (input[i] > 57))
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Вычисляет двоичное представление правой части входной последовательности
        /// </summary>
        /// <param name="input">Входная последовательность</param>
        /// <returns>Двоичное представление</returns>
        private string CodeRightPart(string input)
        {
            string result = String.Empty;
            for (int i = 6; i != input.Length; i++)
            {
                result += r_code[input[i]];
            }
            return result;
        }
        /// <summary>
        /// Вычисление контрольной цифры для входной последовательности
        /// </summary>
        /// <param name="line">Последовательность цифр для вычисления контрольной цифры</param>
        /// <returns>Контрольная цифра (int) </returns>
        private int EvalControlDigit(string line)
        {
            int result = 0;
            for (int i = (line.Length - 1); i != -1; i--)
            {
                int dig = Int32.Parse(line[i].ToString());
                int t = i % 2;
                if (t != 0)
                    result += (dig * 3);
                else
                    result += dig;
            }
            result %= 10;
            result = 10 - result;

            return result % 10;
        }

        /// <summary>
        /// Вычисляет двоичное представление левой части входной последовательности
        /// </summary>
        /// <param name="line">Входная последовательность</param>
        /// <returns>Двоичное представление</returns>
        private String CodeLeftPart(string line)
        {
            String result = String.Empty;
            string[] masks = { "", "356", "346", "345", "256", "236", "234", "246", "245", "235" };//TODO: проверить!!!
            string currentMask = masks[_firstDigit.Value];

            for (int i = 1; i != 7; i++)
            {
                if (currentMask.Contains((i).ToString()))
                    result += g_code[line[i]];
                else
                    result += l_code[line[i]];
            }
            return result;
        }

    }
}
